/*
 * msgserv.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <strings.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>

#include "msgserv.h" /* configuration, and struct definitions */

static char _parity(const char *buff, const int size)
{
	int i;
	char j;
	j=0;
	for (i=0; i<size; i++)
		j=j^buff[i];
	return(j);
}

static int msglcd_parity(const char *buff, const int len)
{
	if ( buff[0] != '*' )
		return(-1);
	if ( buff[1] != len )
		return(-2);
	if ( buff[len-2] != 0 )
		return(-3);
	if ( _parity(buff,len-1) != buff[len-1] )
		return(-4);
	return(0);
}

static int msglcd_test_fd(const int fd, const char mode, const int tim_sec, const int tim_usec)
{
	/* mode = 'r' - test fd for incoming mesg
	 * mode = 'w' - test fd fot outgoing mesg (?)
	 * mode = 'b' - both [:-D */
	fd_set set;
	struct timeval tv;
	tv.tv_sec = tim_sec;
	tv.tv_usec = tim_usec;
	FD_ZERO(&set);
	FD_SET(fd,&set);
	switch (mode) {
	case 'r':
		return(select(fd+1,&set,NULL,NULL,&tv));
	case 'w':
		return(select(fd+1,NULL,&set,NULL,&tv));
	case 'b':
		return(select(fd+1,&set,&set,NULL,&tv));
	}
	return(-2);
}

int msglcd_recv(const int sockfd, char *buff, const int len)
{
	int status;
	/* obtain messege length */
	status = recv(sockfd, buff, 2, MSG_DONTWAIT | MSG_NOSIGNAL );
	if ( status == -1 ) {
		if ( errno == EAGAIN ) {
			status = recv(sockfd,buff,2,MSG_DONTWAIT | MSG_NOSIGNAL );
			if ( status == -1 ) return(-1);
		} else {
			return(-1);
		}
	}
	if ( status == 0 ) return(-5);
	if ( status != 2 ) return(-3);
	
	/* buff[1] - it is the message lenght */
	if ( buff[1] > len )
		return(-2);
	
	/* obtain the rest of the mesage */
	status = recv(sockfd, buff+2, buff[1]-2, MSG_DONTWAIT | MSG_NOSIGNAL );
	if ( status == -1 ) {
		if ( errno == EAGAIN ) {
			status = recv(sockfd,buff,2,MSG_DONTWAIT | MSG_NOSIGNAL );
			if ( status == -1 ) return(-4);
		} else {
			return(-6);
		}
	}
	if ( status == 0 ) 
		return(-5);
	
	status+=2;
	if ( status != buff[1] ) 
		return(-8);
	if ( ( status = msglcd_parity(buff, status) ) < 0)
		return(-10 + status);
	
	return(buff[1]);
}

int msglcd_send(const int sockfd, const char *mesg, const int len)
{
	char buff[256];
	int retval;
	buff[0] = '*';
	buff[1] = len+4;
	memcpy(buff+2,mesg,len);
	buff[len+2] = 0;
	buff[len+3] = _parity(buff,len+3);
	retval = send(sockfd, buff, len+4, MSG_DONTWAIT | MSG_NOSIGNAL);
	if (retval != buff[1])
		return(-1);
	if (retval == -1)
		return(-2);
	return(0);
}

/* 
 * msglcd_handle_server
 * handle ALL incoming messages to client from server
 * RETURN 0 on success, -4 on server closed connetion
 * return negativ on error, sometimes errno should be set ;) 
 * */
int msglcd_handle_server(const int fd)
{
	/* check for any incoming messeges and process all of them */
	char buff[2+MSGLCD_MESG_LENGTH];
	unsigned int retval = 0, status;
	
	while ( (status = msglcd_test_fd(fd, 'r', 0, 0)) > 0 ) {
		status = msglcd_recv(fd, buff,7);
		switch( status ) {
		case -5: /* perr has shutdowned */
			return(-10000);
		case 7:
			if ( !strncmp(buff+2, MSGLCD_MESG_OK, 3) ) {
				retval+=1;
				break;
			}
			if ( !strncmp(buff+2, MSGLCD_MESG_TOMA, 3) ) {
				retval+=100;
				break;
			}
			if ( !strncmp(buff+2, MSGLCD_MESG_PING, 3) ) {
				retval+=10000;
				if (msglcd_send(fd, MSGLCD_MESG_OK, 3) < 0)
					return(-3);
				break;
			}
			if ( !strncmp(buff+2, MSGLCD_MESG_EXIT, 3) )
				return(-4);
			if ( !strncmp(buff+2, MSGLCD_MESG_ERROR, 3) )
				return(-6);
			return(-3000 + status);
		default:
			return(-2000 + status);
		}
	}
	if(status < 0)
		return(-1000 + status);
	return(retval);
}

int msglcd_updat(const int sockfd, const char nr, const char pri, const char *mesg, const int len)
{
	char buff[7+MSG_LEN] = { 
		[0] = '#',
		[1] = MSG_UP,
		[2] = '#',
		[3] = nr,
		[4] = '#',
		[5] = pri,
		[6] = '#',
	};
	int ret;
	
	ret = msglcd_handle_server(sockfd);
	if ( ret < 0 ) {
		return(-5000 + ret);
	}
	if (mesg != NULL)  { /* not deleting message */
		if (len < MSG_LEN) {
			memcpy(buff+7, mesg, len);
			memset(buff+len+7, ' ', MSG_LEN-len);
		} else if (len == MSG_LEN) {
			memcpy(buff+7, mesg, len);
		} else {
			memcpy(buff+7, mesg, MSG_LEN);
		}
		if ( msglcd_send(sockfd, buff, MSG_LEN+7) < 0 ) {
			return(-2);
		}
	} else { 
		/* delete one messege */
		ret = msglcd_send(sockfd, buff, 7);
		if ( ret < 0 ) {
			return(-6000 + ret);
		}
	}
	if ( (ret = msglcd_handle_server(sockfd)) < 0 ) {
		return(-7000 + ret);
	}
	return(0);
}

int msglcd_send_to_lcd(const int sockfd, const char *mesg, const int len) 
{
	char buff[len+5];
	if (mesg == NULL) return(-11);
	sprintf(buff, "#%c###", MSG_LCD);
	memcpy(buff+5, mesg, len);
	
	if ( msglcd_handle_server(sockfd) < 0 ) {
		return(-2);
	}
	if ( msglcd_send(sockfd, buff, len+5) < 0 ) {
		return(-1);
	}
	if ( msglcd_handle_server(sockfd) < 0 ) {
		return(-3);
	}
	
	return 0;
}

void msglcd_del_allmesg(const int sockfd) 
{
	char buff[2] = { [0] = '#', [1] = MSG_DEL };
	msglcd_send(sockfd, buff, 2);
}

int msglcd_socket_connect(const in_addr_t server_ip, const char sign)
{
	int sockfd;
	const char buff[2] = { [0] = '#', [1] = sign };
	struct sockaddr_in their_addr;  /* connector's address information */
	int status;

	sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		return(-1);
	}
	
	bzero(&their_addr, sizeof(their_addr));        /* zero the rest of the struct */
	their_addr.sin_family = PF_INET;         /* host byte order */
	their_addr.sin_port = htons(MSGLCD_PORT);     /* short, network byte order */
	their_addr.sin_addr.s_addr = server_ip ; /* auto-fill with my IP */
	
	status = connect(sockfd,(struct sockaddr *)&their_addr, sizeof(struct sockaddr_in));
	if ( status == -1 )  {
		close(sockfd); 
		return(-1);
	}
	status = msglcd_send(sockfd, buff, 2);
	if (status < 0 ) {
		close(sockfd);
		return(-100+status);
	}
	status = msglcd_handle_server(sockfd);
	if (status < 0) {
		close(sockfd);
		return(-200+status);
	}
	return(sockfd);
}

void msglcd_socket_disconnect(const int sockfd) 
{
	msglcd_send(sockfd, MSGLCD_MESG_EXIT, 3);
	close(sockfd);
}
